#include "commDataIap.h"
#include "flashUser.h"
#include "osal.h"
#include "module_config.h"

#define IAPKEYENABLE

static uint8_t iapIsErased = 0;

static uint32_t gIapCheckSumBin = 0;
static uint32_t gIapCheckSumBinRd = 0;
static uint32_t gIapFlashOffset = 0;
static int32_t  gs32LastPackNo = -1;

static md5_t g_IAP_md5_t;
static char gCharMD5FileSet[HASHSIZE];
static char gCharMD5File[HASHSIZE];

static firmwareUpdateStatusTypeDef gFirmwareStatus;
typedef struct
{
    uint8_t ota_status;
    uint8_t power_reset;
}IapData_st;

static IapData_st iap_data;

uint8_t getOTADownloadFlag(void)
{
    read_ota_data(&gFirmwareStatus);
    return gFirmwareStatus.download_flag;
}

uint8_t get_ota_status(void)
{
    return iap_data.ota_status;
}

void set_ota_status(uint8_t value)
{
    iap_data.ota_status = value;
}

void clearOTADownloadFlag(void)
{
    getOTADownloadFlag();
    if((gFirmwareStatus.download_flag != 0) || (gFirmwareStatus.update_flag != 0))
    {
        gFirmwareStatus.download_flag = 0;
        gFirmwareStatus.update_flag = 0;
        write_ota_data(&gFirmwareStatus);
        LOG_TS(AM_LOG_LEVEL_INFO, "Flash", "clear ota flag\n");
    }
}

/**
  * @brief  
  * @param  void
  * @retval 0 : success
  * @retval -1 : error
  */
int8_t sendIapAck(uint8_t u8SrcId, uint8_t ack_code, uint8_t* pu8Data, uint8_t u8Len)
{
    return packData2Ap(pu8Data, u8Len, ack_code, COMM_CMD_IAP_ACK, ID_SELF, u8SrcId);
}

/**
  * @brief  
  * @param  void
  * @retval 0 : success
  * @retval -1 : error
  */
int8_t commCmdIapBeginProc(uint8_t *pu8Data, uint8_t u8DataSize, uint8_t u8SrcId)
{
    int8_t s8RetVal = 0;
    uint8_t ack[5] = {0};

    /* self check and power down return */
    // if (gmowerData.powerOffState == 1)
    // {
    //     ack[0] = COMM_IAPERROR_STATUS;
    //     sendIapAck(u8SrcId, COMM_CMD_IAP_BEGIN, ack, 5);
    //     LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "poweroff error\n");
    //     return -10;
    // }

    if (20 != u8DataSize)
    {
        ack[0] = COMM_IAPERROR_PARAM;
        sendIapAck(u8SrcId, COMM_CMD_IAP_BEGIN, ack, 5);
        LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "Param size %d != 8 \n", u8DataSize);
        return -11;
    }

    memcpy(&gFirmwareStatus.firmware_size, pu8Data, 4); /*  get firmware size  */
    memcpy(&gCharMD5FileSet, pu8Data + 4, HASHSIZE);       /*  get firmware MD5 of ciphertext */

    /*  set flash address  */
    gIapFlashOffset     = 0;
    gIapCheckSumBin     = 0;
    gs32LastPackNo      = -1;
    gFirmwareStatus.download_flag = 0;
    iap_data.ota_status         = 0;
    memset(gCharMD5File, 0, HASHSIZE);       /*  get firmware MD5 of ciphertext */

    LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "iap size:0x%x, md5:%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x\n", gFirmwareStatus.firmware_size, \
        gCharMD5FileSet[0], gCharMD5FileSet[1], gCharMD5FileSet[2], gCharMD5FileSet[3], \
        gCharMD5FileSet[4], gCharMD5FileSet[5], gCharMD5FileSet[6], gCharMD5FileSet[7], \
        gCharMD5FileSet[8], gCharMD5FileSet[9], gCharMD5FileSet[10], gCharMD5FileSet[11], \
        gCharMD5FileSet[12], gCharMD5FileSet[13], gCharMD5FileSet[14], gCharMD5FileSet[15]);
    

    /*  detect if size is too large  */
    if(gFirmwareStatus.firmware_size > APP_MAX_SIZE)
    {
        s8RetVal = -12;
        ack[0] = COMM_IAPERROR_SIZE;
        LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "file size too large 0x%x\n", gFirmwareStatus.firmware_size);
    }
    else
    {
        if (iapIsErased == 0)
        {
            /*  erase the flash  */
            s8RetVal = flash_program_erase(APP_TEMP_B_START_ADDR,gFirmwareStatus.firmware_size);
            if (BSP_FLASH_ERROR_NULL == s8RetVal)
            {
                iap_data.ota_status = 1;
                ack[0] = COMM_IAP_OK;
                iapIsErased = 1;
            }
            else
            {
                ack[0] = COMM_IAPERROR_ERASE;
                iapIsErased = 0;
            }

            LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "erase flash finish %d\n", s8RetVal);
        }
        else
        {
            iap_data.ota_status = 1;
            ack[0] = COMM_IAP_OK;
            LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "flash has been erased before, ignore\n");
        }    
    }
    
    sendIapAck(u8SrcId, COMM_CMD_IAP_BEGIN, ack, 5);

    return s8RetVal;
}

/**
  * @brief  
  * @param  void
  * @retval 0 : success
  * @retval -1 : error
  */
int8_t commCmdIapTransProc(uint8_t *pu8Data, uint8_t u8DataSize, uint8_t u8Index, uint8_t u8SrcId)
{
    static int32_t s_s32Cnt = 0;
    int32_t  s32CurrPackNo;
    uint32_t u32RemainLen = 0;
    int8_t  u8RetVal = 0;
    uint8_t ack[5] = {0};

    /*  variables for decrypt  */
    static int32_t pos = 0;
    static uint8_t previous[8];
    static uint8_t key[16];
    int32_t retMD5 = 1;

    if(iap_data.ota_status == 0)
    {
        ack[0] = COMM_IAPERROR_STATUS;
        sendIapAck(u8SrcId, COMM_CMD_IAP_TRANS, ack, 5);
        LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "not ota state, otaflag %d\n", iap_data.ota_status);
        return -10;
    }

    if (4 >= u8DataSize)
    {
        ack[0] = COMM_IAPERROR_PARAM;
        sendIapAck(u8SrcId, COMM_CMD_IAP_TRANS, ack, 5);
        LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "Param size %d <= 4 \n", u8DataSize);
        return -11;
    }

    /* get current package no */
    memcpy(&s32CurrPackNo, pu8Data, 4);
    pu8Data     += 4;   /* skip package no, 4 bytes */
    u8DataSize  -= 4;

    if ((-1 == gs32LastPackNo) && (0 == s32CurrPackNo)) /* the first package */
    {
        LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "Rcv the 1st pkg!\n");
        pos = 0;
        memcpy(key, (uint8_t*)g_factory_nessary.iapKeyArray, 16);

        md5_init(&g_IAP_md5_t);
    }

    if(1 == (s32CurrPackNo - gs32LastPackNo))
    {
        retMD5 = md5_update(&g_IAP_md5_t, (const char *)pu8Data, u8DataSize);

#ifdef IAPKEYENABLE
        /*  decrypt data  */
        nb_bin_decrypt_chunk(pu8Data, u8DataSize, (int*)&pos, previous, key);
#endif 

        /*  calculate remain length  */
        u32RemainLen = gFirmwareStatus.firmware_size - gIapFlashOffset;
        if(u32RemainLen > 128)
        {
            for(uint8_t i = 0; i < u8DataSize; i += 4)
            {
                gIapCheckSumBin += (*(uint32_t*)(pu8Data + i));
            }            
        }
        else
        {
            for(uint8_t i = 0; i < u32RemainLen - 4; i += 4)
            {
                gIapCheckSumBin +=  (*(uint32_t*)(pu8Data + i));
            }
            memcpy(&gIapCheckSumBinRd, pu8Data + u32RemainLen - 4, 4);
        }

        iapIsErased = 0;

        LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "pack No %d, off 0x%x, len 0x%x, remain 0x%x\n", s32CurrPackNo, gIapFlashOffset, u8DataSize, u32RemainLen);
        if(flash_program_write(APP_TEMP_B_START_ADDR + gIapFlashOffset, pu8Data, u8DataSize) != 0)
        {
            u8RetVal = -12;
            ack[0] = COMM_IAPERROR_WRITEFLASH;
            LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "Wr flash err->PackNo: %d\n", s32CurrPackNo);  
        }
        else
        {
            ack[0]    = COMM_IAP_OK;
            gs32LastPackNo    = s32CurrPackNo;
            gIapFlashOffset  += u8DataSize;
            s_s32Cnt = 0;
            if (u32RemainLen <= 128)  /* the last package */
            {
                // todo : check if the index also indicate the last package
                gIapCheckSumBin = ~((gIapCheckSumBin<<16) | (gIapCheckSumBin>>16));
                if(retMD5 == 0)
                {
                    md5_update(&g_IAP_md5_t, NULL, 0);
                }
                md5_finish(&g_IAP_md5_t, gCharMD5File);
            }
        }
    }
    else if (gs32LastPackNo >= s32CurrPackNo)
    {
        if (s_s32Cnt++ < 5)
        {
            ack[0] = COMM_IAP_OK;
        }
        else
        {
            ack[0] = COMM_IAPERROR_INDEX;
        }
        LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "PackNo Err1: last %d, curr %d, cnt %d\n", gs32LastPackNo, s32CurrPackNo, s_s32Cnt);
    }
    else
    {
        u8RetVal = -11;
        ack[0] = COMM_IAPERROR_INDEX;
        LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "PackNo Err2: last %d, curr %d\n", gs32LastPackNo, s32CurrPackNo);  
    }
    
    /*  send ack  */
    memcpy(&ack[1], &gs32LastPackNo, 4);
    sendIapAck(u8SrcId, COMM_CMD_IAP_TRANS, ack, 5);

    return u8RetVal;
}

/**
  * @brief  
  * @param  void
  * @retval 0 : success
  * @retval -1 : error
  */
int8_t commCmdIapVerifyProc(uint8_t *pu8Data, uint8_t u8DataSize, uint8_t u8SrcId)
{
    int8_t  u8RetVal = 0;
    uint8_t ack[5] = {0};

    LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "checksum IapCheckSumBin=%04x gIapCheckSumBin=%04x!\n", gIapCheckSumBin, gIapCheckSumBinRd);
    if (
#ifdef IAPKEYENABLE        
        (gIapCheckSumBin  == gIapCheckSumBinRd) &&
#endif 
        (memcmp(gCharMD5FileSet, gCharMD5File, HASHSIZE) == 0) &&
        (1 == iap_data.ota_status))
    {
        gFirmwareStatus.download_flag = 1;
        gFirmwareStatus.check_sum = gIapCheckSumBin;

        /*  save firmware status  */
        if(write_ota_data(&gFirmwareStatus))
        {
            ack[0] = COMM_IAPERROR_WRITEFLASH;
            LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "Wr OTA Flag failed!\n");
        }
        else
        {
            ack[0] = COMM_IAP_OK;
            LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "Rcv Reset Ok!\n");
        }
    }
    else
    {
        u8RetVal = -1;
        if (1 == iap_data.ota_status)
        {
            ack[0] = COMM_IAPERROR_CRC;
        }
        else
        {
            ack[0] = COMM_IAPERROR_RESET;
        }
        LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "Reset Err: %d, 0x%x, 0x%x; md5:%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x%x\n", iap_data.ota_status, gIapCheckSumBin, gIapCheckSumBinRd, \
            gCharMD5File[0], gCharMD5File[1], gCharMD5File[2], gCharMD5File[3], \
            gCharMD5File[4], gCharMD5File[5], gCharMD5File[6], gCharMD5File[7], \
            gCharMD5File[8], gCharMD5File[9], gCharMD5File[10], gCharMD5File[11], \
            gCharMD5File[12], gCharMD5File[13], gCharMD5File[14], gCharMD5File[15]);
    }
    
    /*  ack  */
    sendIapAck(u8SrcId, COMM_CMD_IAP_VERIFY, ack, 5);
    return u8RetVal;
}

static uint8_t IapResetCheck(uint8_t u8ResetReason)
{
    LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "iap reset reason %d, otaflg %d, downflg %d\n", u8ResetReason, iap_data.ota_status, gFirmwareStatus.download_flag);
    if ((1 == iap_data.ota_status) && (1 == gFirmwareStatus.download_flag))
    {
        iap_data.power_reset = u8ResetReason;
        return COMM_IAP_OK;
    }

    return COMM_IAPERROR_RESET;
}

/**
  * @brief  
  * @param  void
  * @retval 0 : success
  * @retval -1 : error
  */
int8_t commCmdIapResetProc(uint8_t u8SrcId)
{
    uint8_t ack[5] = {0};

    ack[0] = IapResetCheck(2);
    sendIapAck(u8SrcId, COMM_CMD_IAP_RESET, ack, 5);
    return 0;
}


/**
  * @brief  
  * @param  void
  * @retval 0 : success
  * @retval -1 : error
  */
int8_t commCmdIapResetNowProc(uint8_t u8SrcId)
{
    uint8_t ack[5] = {0};

    ack[0] = IapResetCheck(3);
    sendIapAck(u8SrcId, COMM_CMD_IAP_RESET_NOW, ack, 5);
    return 0;
}


/**
  * @brief  The ecu resets after the host switches off
  * @param  void
  * @retval 0 : success
  * @retval -1 : error
  */
int8_t ecuIapWaitResetProc(void)
{
    static uint32_t s_u32Cnt = 0;
    static uint8_t state = 0;
    // uint8_t ret = 0;

    if ((iap_data.power_reset !=2) && 
        (iap_data.power_reset !=3) && 
        (iap_data.power_reset !=4))
    {
        /*  1: key power down, processed in power down mode
            2: iap power reset, from AP
            3: iap power reset now, from NPI
            4: reset, from app
        */
        return 0;
    }

    switch (state)
    {
    #if defined (MODULE_INCLUDE_MOTOR)
        /* close motor */
        case 0:
            motorInterface_powerDownClearFlag();
            LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "motor start!\n");
            state++;
            break;
        case 1:
            ret = motorInterface_powerDownProc(1, 10);
            if (ret != 0)
            {
                LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "motor complete %d!\n", ret);
                state++;
                osal_delay_ms(100);
                power_ctrl(ECU_5V0_PERIPHERAL_EN, 0);
                power_ctrl(ECU_VCU_PWR_EN, 0);
            }
            break;
    #endif

        default:
            if (s_u32Cnt++ <= 5)
            {
                return 0;
            }

            if ((s_u32Cnt > 50) ||\
                (iap_data.power_reset == 3) ||\
                (iap_data.power_reset == 4 /* && cpuInterface.shutDown.shutDownEnd == 1  */))
            {
                LOG_TS(AM_LOG_LEVEL_FATAL, "OTA", "restart:%d %d\n",iap_data.power_reset);
                osal_delay_ms(500); 
                iap_data.ota_status = 0;
                HAL_NVIC_SystemReset();
            }
            break;
    }
    return 0;    
}
